package com.hardson.toolkit.util;


import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * lambda异常抛出工具类
 * 附注1：这个LambdaExceptionUtil类中的rethrow方法可以毫无畏惧地使用，并且是可以在任何情况下使用..
 * 这在很大程度上要感谢用户@PaoloC，他帮助解决了最后一个问题：
 * 现在编译器将要求您添加抛出子句，这一切就好像您可以在Java 8流上本地抛出检查异常一样。
 * <p>
 * 附注2：这个uncheck方法是类LambdaExceptionUtil中的额外的方法，如果您不想使用它们，可以将它们安全地从类中删除。
 * 如果您确实使用了它们，请在了解以下用例、优点/缺点和限制之后，谨慎使用它们：
 * <p>
 * 1.您可以使用uncheck方法，如果您正在调用一个方法，该方法实际上永远不能抛出它声明的异常。例如：新字符串(byteArr，“UTF-8”)
 * 抛出UnsuptedEncodingException，但是Java规范保证UTF-8始终存在。在这里，“抛出”声明是一个讨厌的问题，
 * 任何用最少的样板使其保持沉默的解决方案都是受欢迎的：String text = uncheck(() -> new String(byteArr, "UTF-8"));
 * <p>
 * 2.您可以使用uncheck方法，如果您正在实现一个严格的接口，而您没有添加抛出声明的选项，那么抛出异常是完全合适的。
 * 包装一个异常只是为了获得抛出它的特权，结果会产生带有虚假异常的堆栈跟踪，这些异常不会提供关于实际出错的信息。
 * 一个很好的例子是Runnable.run()，它不会抛出任何检查过的异常。
 * <p>
 * 3.无论如何，如果您决定使用uncheck方法，请注意抛出检查过的异常而没有抛出子句的两个后果：
 * 1) 调用代码将无法按名称捕获它(如果您尝试，编译器会说：异常永远不会抛出在相应的try语句中)。
 * 它会出现气泡，并且可能会被某些“捕捉异常”或“捕捉Throwable”捕获，这可能是您想要的。
 * 2) 这违反了“最不吃惊”的原则：它将不再足以抓住RuntimeException才能保证捕捉所有可能的异常。
 * 因此，我认为这不应该在框架代码中完成，而应该只在您完全控制的业务代码中完成。
 */
public final class LambdaExceptionUtil {
    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
        void accept(T t, U u) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    @FunctionalInterface
    public interface Supplier_WithExceptions<T, E extends Exception> {
        T get() throws E;
    }

    @FunctionalInterface
    public interface Runnable_WithExceptions<E extends Exception> {
        void run() throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .
     * forEach(rethrowConsumer(ClassNameUtil::println));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(
            Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwAsUnchecked(exception);
            }
        };
    }

    public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(
            BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
        return (t, u) -> {
            try {
                biConsumer.accept(t, u);
            } catch (Exception exception) {
                throwAsUnchecked(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(
            Function_WithExceptions<T, R, E> function) throws E {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwAsUnchecked(exception);
                return null;
            }
        };
    }

    /**
     * rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
     */
    public static <T, E extends Exception> Supplier<T> rethrowSupplier(
            Supplier_WithExceptions<T, E> function) throws E {
        return () -> {
            try {
                return function.get();
            } catch (Exception exception) {
                throwAsUnchecked(exception);
                return null;
            }
        };
    }

    /**
     * uncheck(() -> Class.forName("xxx"));
     */
    public static void uncheck(Runnable_WithExceptions t) {
        try {
            t.run();
        } catch (Exception exception) {
            throwAsUnchecked(exception);
        }
    }

    /**
     * uncheck(() -> Class.forName("xxx"));
     */
    public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) {
        try {
            return supplier.get();
        } catch (Exception exception) {
            throwAsUnchecked(exception);
            return null;
        }
    }

    /**
     * uncheck(Class::forName, "xxx");
     */
    public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
        try {
            return function.apply(t);
        } catch (Exception exception) {
            throwAsUnchecked(exception);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
        throw (E) exception;
    }
    //以下的@Test,ideaiu中会报“程序包org.junit不存在”的错误，原因找不到

    /*@Test
    public void test_Consumer_with_checked_exceptions() throws ClassNotFoundException {
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .forEach(rethrowConsumer(System.out::println));
    }

    @Test
    public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
        List<Class<?>> classes1 = Stream.of("Object", "Integer", "String")
                .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                .collect(Collectors.toList());

        List<Class<?>> classes2 = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(rethrowFunction(Class::forName))
                .collect(Collectors.toList());
    }

    @Test
    public void test_Supplier_with_checked_exceptions() throws UnsupportedEncodingException {
        Collector.of(rethrowSupplier(
                () -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, StandardCharsets.UTF_8))
        ), StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

    @Test
    public void test_uncheck_exception_thrown_by_method() {
        Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

        Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

    @Test(expected = ClassNotFoundException.class)
    public void test_if_correct_exception_is_still_thrown_by_method() {
        Class clazz3 = uncheck(Class::forName, "INVALID");
    }*/
}